using System;
using System.Collections;
using System.Threading;
using Gtk;
using JmShared;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using NetworkCommsDotNet;
using System.Text.RegularExpressions;
using System.Linq;
using System.Diagnostics;
using MessageType = JmShared.MessageType;

namespace JiveMessengerLinux
{
    [System.ComponentModel.ToolboxItem(true)]
    public partial class ChatControl : Gtk.Bin
    {
        private readonly static Regex HyperLinkRegex = new Regex(@"(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-ZÆæÅåØø0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-ZÆæÅåØø0-9\&amp;%_\./-~-\(\),\'\[\]+\=#]*)?");
        private System.Timers.Timer _lastMessageReceivedTimer;
        public object Tag { get; set; }
        public int SessionID { get; set; }
        private bool LastParagraphHadLine { get; set; }
        private List<LogElement> Log { get; set; }
        public Dictionary<string, DateTime> LastReceivedMessageTyping { get; private set; }
        private DateTime LastMessageReceivedTime { get; set; }
        private DateTime LastSentNowWritingUpdate { get; set; }
        private UserInfo LastUserWritten { get; set; }
        private DateTime LastNudge { get; set; }
        private bool hoveringOverLink = false;
        Gdk.Cursor handCursor, regularCursor;
        private EmoticonWindow EmoticonWin { get; set; }
        public event EventHandler<WriteMessageEventArgs> WriteMessageEvent;
        public event EventHandler<EventArgs> NowWritingEvent, NudgeEvent;

        private Dictionary<int, Gtk.Image> ConversationImages { get; set; }

        private Dictionary<int, Gtk.Image> MessageImages { get; set; }

        private Dictionary<Gtk.TextTag, string> DocumentHyperlinks { get; set; }
        public static bool NeedEmoticonRefresh { get; set; }

        public ChatControl()
        {
            this.Build();

            //	this.HeightRequest = 400;
            this.LastReceivedMessageTyping = new Dictionary<string, DateTime>();
            this.Log = new List<LogElement>();
            this.ConversationImages = new Dictionary<int, Gtk.Image>();
            this.MessageImages = new Dictionary<int, Image>();
            this.txtViewConversation.WidgetEventAfter += new Gtk.WidgetEventAfterHandler(EventAfter);
            this.txtViewConversation.VisibilityNotifyEvent += new Gtk.VisibilityNotifyEventHandler(VisibilityNotify);
            this.txtViewConversation.MotionNotifyEvent += new Gtk.MotionNotifyEventHandler(MotionNotify);
            this.DocumentHyperlinks = new Dictionary<Gtk.TextTag, string>();
            handCursor = new Gdk.Cursor(Gdk.CursorType.Hand2);
            regularCursor = new Gdk.Cursor(Gdk.CursorType.Xterm);
            //            this.lastIter = txtViewConversation.Buffer.StartIter;
            this.txtViewConversation.ModifyFont(StaticSettings.FontDescription);
            this.txtViewMessage.ModifyFont(StaticSettings.FontDescription);
            Gdk.Color grey = new Gdk.Color(160, 150, 150);
            this.lblStatus.Text = "";
            this.lblStatus.ModifyFg(StateType.Normal, grey);

            this.bnSend.Clicked += (s, e) =>
                {
                    //  string message = this.txtViewMessage.Buffer.Text;//this.GetInputText(true);
                    ProcessAndSendInput();
                    //if (!String.IsNullOrEmpty(message))
                    //{
                    //    SendMessage(message);
                    //}
                };
           
           
            this.txtViewMessage.Buffer.Changed += (s, e) =>
                {
                    foreach (KeyValuePair<string, string> kvp in StaticSettings.GetEmoticons(Client.CurrentUserInfo, true, true))
                    {
                        TextIter start;
                        TextIter end;
                        if (this.txtViewMessage.Buffer.EndIter.BackwardSearch(kvp.Key, 0, out start, out end, txtViewConversation.Buffer.StartIter))
                        {
                            string link = kvp.Value;
                            Gtk.Image img = System.IO.Path.IsPathRooted(link) ? new Gtk.Image(link) : new Gtk.Image(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("JiveMessengerLinux.Resources." + link));
                            img.Data.Add(kvp.Key, kvp.Value);
                            this.MessageImages.Add(start.Offset, img);
                            if (!this.txtViewMessage.Buffer.Data.ContainsKey(start.Offset))
                                this.txtViewMessage.Buffer.Data.Add(start.Offset, kvp.Key);
                            this.txtViewMessage.Buffer.Delete(ref start, ref end);
                            this.RefreshImages(this.MessageImages, this.txtViewMessage);

                        }
                    }
                    int endoffset = this.txtViewMessage.Buffer.EndIter.Offset;
                    if (endoffset == 0)
                        this.txtViewMessage.Buffer.Data.Clear();
                    List<int> keysToRemove = new List<int>();
                    foreach (DictionaryEntry dic in txtViewMessage.Buffer.Data)
                    {
                        if ((int)dic.Key > endoffset)
                            keysToRemove.Add((int)dic.Key);
                    }
                    foreach (int i in keysToRemove)
                    {
                        this.txtViewMessage.Buffer.Data.Remove(i);
                    }
                };

            StartTimers();
        }

        private void StartTimers()
        {
            _lastMessageReceivedTimer = new System.Timers.Timer
                {
                    Interval = 200
                };
            _lastMessageReceivedTimer.Elapsed += new System.Timers.ElapsedEventHandler(LastMessageReceivedTimer_Tick);
            _lastMessageReceivedTimer.Start();
        }
        /// <summary>
        /// Timer that updates NowWriting
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LastMessageReceivedTimer_Tick(object sender, EventArgs e)
        {
            bool clear = true;
            if (this.LastUserWritten == null)
                return;
            string isWritingText = "";
            bool someoneWriting = false;

            //Window w = Window.GetWindow(this);
            //if (w == null)
            //    return;
            Dictionary<string, DateTime> lastReceived = this.LastReceivedMessageTyping;
            if (lastReceived == null)
                return;
            foreach (KeyValuePair<string, DateTime> kvp in lastReceived)
            {
                if (kvp.Value == DateTime.MinValue || !(DateTime.Now.Subtract(kvp.Value).TotalSeconds <= 4))
                    continue;

                someoneWriting = true;
                if (Client.ConnectedClients.ContainsKey(kvp.Key))
                    isWritingText += Client.ConnectedClients[kvp.Key].PrettyName + ",";

            }
            if (someoneWriting)
                clear = false;
            Gtk.Application.Invoke(delegate
            {
                if (clear && this.LastMessageReceivedTime != DateTime.MinValue && !this.LastUserWritten.Equals(Client.CurrentUserInfo))
                {
                    this.lblStatus.Data.Clear();
                    isWritingText = String.Format("Last message received at {0} on {1}.", this.LastMessageReceivedTime.ToShortTimeString(), this.LastMessageReceivedTime.ToShortDateString());
                    this.lblStatus.Data.Add(0, isWritingText);
                }
                else if (!String.IsNullOrEmpty(isWritingText) && !clear)
                {
                    isWritingText = isWritingText.TrimEnd(',');
                    isWritingText += " is writing.";
                }
                if (String.IsNullOrEmpty(isWritingText) && this.lblStatus.Data.ContainsKey(0))
                    isWritingText = this.lblStatus.Data[0].ToString();

                this.lblStatus.Text = isWritingText;
            });

        }

        private void ProcessAndSendInput()
        {
            string message = "";// this.txtViewMessage.Buffer.Text;//this.GetInputText(true);

            //ArrayList al = new ArrayList(this.txtViewMessage.Buffer.Data.Keys);
            //al.Sort();
            //int prevLength = 0;
            //foreach (int i in al)//(DictionaryEntry kvp in this.txtViewMessage.Buffer.Data)
            //{
            //    message = message.Insert((int)i + prevLength, this.txtViewMessage.Buffer.Data[i].ToString());
            //    prevLength += this.txtViewMessage.Buffer.Data[i].ToString().Length - 1;
            //}

            for (int i = 0; i < this.txtViewMessage.Buffer.EndIter.Offset; i++)
            {
                TextIter iter = this.txtViewMessage.Buffer.GetIterAtOffset(i);
                TextChildAnchor anchor = iter.ChildAnchor;
                if (anchor == null)
                {
                    message += iter.Char;
                }
                else
                {
                    Gtk.Image img = anchor.Widgets[0] as Gtk.Image;
                    if (img != null)
                    {
                        message += img.Data.Keys.Cast<string>().First();
                    }
                }
            }


            txtViewMessage.Buffer.Data.Clear();
            if (!String.IsNullOrEmpty(message))
            {
                SendMessage(message);
            }

        }

        [GLib.ConnectBefore]
        private void OnTxtViewMessageKeyPressEvent(object o, Gtk.KeyPressEventArgs args)
        {
            Gtk.TextView tw = (Gtk.TextView)o;
            var key = args.Event.Key;
            //			if(key == Gdk.Key.Return)
            //			{
            //
            //			}

            if (key == Gdk.Key.Return && args.Event.State != Gdk.ModifierType.ShiftMask)
            {
                ProcessAndSendInput();
                args.RetVal = true;
            }
            //            else if (args.Event.State== Gdk.ModifierType.ShiftMask && e.Key == Key.Up)
            //            {
            //                if (this.AvalonMessage.LineCount == 1)
            //                {
            //                    this.AvalonMessage.SelectAll();
            //                }
            //            }
            //            else if (Keyboard.Modifiers == ModifierKeys.Shift && e.Key == Key.Down)
            //            {
            //                if (this.AvalonMessage.LineCount == 1)
            //                {
            //                    this.AvalonMessage.Select(this.AvalonMessage.Text.Length, 0);
            //                }
            //            }
            else
            {
                if (DateTime.UtcNow.Subtract(this.LastSentNowWritingUpdate).TotalSeconds > 3)
                {
                    NowWritingEvent(this, null);
                    this.LastSentNowWritingUpdate = DateTime.UtcNow;
                    //tw.Buffer.ApplyTag(Client.TextTags[Client.CurrentUserInfo.UserID+"_" + Client.TextTagCount[Client.CurrentUserInfo.UserID]], tw.Buffer.StartIter, tw.Buffer.EndIter);
                }
            }
        }

        private void SendMessage(string message)
        {
            ChatMessage msg = new ChatMessage
            {
                MessageType = JmShared.MessageType.Message,
                Message = message,
                UserInfo = new UserInfo[1],
                Sender = Client.CurrentUserInfo
            };

            this.txtViewMessage.Buffer.Text = "";
            this.DisplayMessage(msg.Message, Client.CurrentUserInfo, msg.MessageType);
            WriteMessageEvent(this, new WriteMessageEventArgs(msg));
            //            if (this.BlinkScrollbarThread != null && this.BlinkScrollbarThread.IsAlive)
            //            {
            //                this.IsScrollbarBlinking = false;
            //                this.BlinkScrollbarThread = null;
            //
            //            }
        }

        private static Dictionary<int, object> SplitUrlAndEmoticons(string text, UserInfo user)
        {
            Dictionary<int, object> objects = new Dictionary<int, object>();
            Regex UrlMatch = HyperLinkRegex;
            MatchCollection col = UrlMatch.Matches(text);
            SortedList<int, TextMatch> foundTextMatches = new SortedList<int, TextMatch>();
            foreach (Match m in col)
            {
                foundTextMatches.Add(m.Index, new TextMatch() { type = TextMatchType.URL, Text = m.Value });
            }
            SerializableDictionary<string, string> allEmoticons = (StaticSettings.GetEmoticons(user, true, true));
            string[] keys = allEmoticons.Keys.ToArray();
            foreach (string str in keys)
            {
                if (String.IsNullOrEmpty(allEmoticons[str]))
                    continue;
                List<int> indexes = text.AllIndexesOf(str);
                foreach (int i in indexes)
                {
                    foundTextMatches.Add(i, new TextMatch() { type = TextMatchType.Emoticon, Text = str });
                }
            }
            int startindex = 0;

            foreach (KeyValuePair<int, TextMatch> kvp in foundTextMatches)
            {
                if (kvp.Key < startindex)
                    continue;

                string leading = text.Substring(startindex, kvp.Key - startindex);
                if (!String.IsNullOrEmpty(leading))
                    objects.Add(startindex, leading);
                switch (kvp.Value.type)
                {
                    case TextMatchType.Emoticon:
                        {
                            string emoticon = kvp.Value.Text;
                            string link = allEmoticons[emoticon];
                            Gtk.Image img = System.IO.Path.IsPathRooted(link) ? new Gtk.Image(link) : new Gtk.Image(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("JiveMessengerLinux.Resources." + link));

                            img.Data.Add(emoticon, emoticon);
                            //                        ImageBehavior.SetAnimatedSource(img, img.Source);
                            objects.Add(kvp.Key, img);
                            break;
                        }
                    case TextMatchType.URL:
                        {
                            string url = kvp.Value.Text;
                            if (url.ToLower().StartsWith("www"))
                                url = "http://" + url;


                            //                        link.Inlines.Add(kvp.Value.Text);
                            //                        link.Tag = kvp.Value.Text;
                            objects.Add(kvp.Key, new JiveHyperLink(url));
                            break;
                        }
                }
                startindex = kvp.Key + kvp.Value.Text.Length;
            }


            if (foundTextMatches.Count == 0)
                objects.Add(0, text);
            else
            {
                string lastText = text.Substring(startindex, text.Length - startindex);
                if (!String.IsNullOrEmpty(lastText))
                    objects.Add(startindex, lastText);
            }
            return objects;
        }

        public void ClearLog()
        {
            if (this.Log != null)
                this.Log.Clear();
            else
                this.Log = new List<LogElement>();
        }
        //clears text controls, logs and settings
        public void Reset()
        {
            this.LastParagraphHadLine = false;
            this.txtViewConversation.Buffer.Text = "";

            this.LastSentNowWritingUpdate = DateTime.MinValue;
            this.LastNudge = DateTime.MinValue;
            this.txtViewMessage.Buffer.Text = "";
            this.LastUserWritten = null;
            this.LastReceivedMessageTyping = new Dictionary<string, DateTime>();
            //			this.ClientLines = new Dictionary<int, UserInfo>();
            this.ClearLog();
        }

        /// <summary>
        /// Loads the chat log for this control from xml
        /// </summary>
        private List<LogElement> LoadLog()
        {
            List<LogElement> logs = new List<LogElement>();
            int lastSession = -1;
            string chatTarget = "";
            XmlReaderSettings settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true };
            //			this.Dispatcher.Invoke((Action)(() =>
            //			                                {
            if (Tag != null)
            {
                UserInfo target = (UserInfo)Tag;
                chatTarget = target.UserName;
            }
            else
                chatTarget = "Main chat";
            //			}));
            FileInfo fi = new FileInfo(StaticMethods.GenerateLogName(Client.CurrentUserInfo.UserName, chatTarget, StaticSettings.LogFolder));
            if (!fi.Exists)
            {
                fi.Create().Close();
                //throw new FileNotFoundException("ChatLog", fi.FullName);
            }
            XmlReader r = XmlReader.Create(fi.FullName, settings);
            try
            {
                r.Read();
                r.Read();
                r.Read();
                while (!r.EOF)
                {
                    if (r.Name == "Log" && !r.IsStartElement())
                        break; // end of it all
                    if (r.Name == "Message" && r.IsStartElement())
                    {
                        LogElement l = new LogElement(r.ReadSubtree());
                        if (l.IsOK())
                        {
                            logs.Add(l);
                            if (lastSession < l.SessionID)
                                lastSession = l.SessionID;
                        }
                        //r.Read();
                    }
                    r.Read();
                }
            }
            catch (Exception err)
            {
                NetworkComms.Logger.ErrorException("Error loading log", err);
            }
            finally
            {
                r.Close();
                this.SessionID = lastSession + 1;

            }
            return logs;
        }

        public void WriteLog(string logname)
        {
            if (Client.CurrentUserInfo == null)
                return;

            if (String.IsNullOrEmpty(logname))
                return;
            List<LogElement> existingLogs = this.LoadLog();

            XmlWriterSettings settings = new XmlWriterSettings { Indent = true, NewLineOnAttributes = true };

            FileInfo fi = new FileInfo(StaticMethods.GenerateLogName(Client.CurrentUserInfo.UserName, logname, StaticSettings.LogFolder));
            if (fi.Directory != null && !fi.Directory.Exists)
            {
                //Directory.CreateDirectory(fi.DirectoryName);
                fi.Directory.Create();
            }
            if (!File.Exists(StaticMethods.CombinePath(StaticSettings.LogFolder, "MessageLog.xsl")))
                File.WriteAllText(StaticMethods.CombinePath(StaticSettings.LogFolder, "MessageLog.xsl"), StaticMethods.GetEmbeddedStringResource("JiveMessengerLinux.Resources.MessageLog.xsl"));
            FileStream fs = !File.Exists(fi.FullName + "temp") ? File.Create(fi.FullName + "temp") : StaticMethods.WaitForFile(fi.FullName + "temp", FileMode.Open, FileAccess.ReadWrite);
            if (fs == null)
                return;
            XmlWriter w = XmlWriter.Create(fs, settings);
            const string strPi = "type='text/xsl' href='../MessageLog.xsl'";
            w.WriteProcessingInstruction("xml-stylesheet", strPi);
            try
            {
                w.WriteStartElement("Log");
                existingLogs.Sort();
                this.Log.Sort();
                foreach (LogElement oldL in existingLogs)
                    oldL.WriteXml(w);
                foreach (LogElement l in this.Log)
                {
                    l.SessionID = this.SessionID;
                    l.WriteXml(w);
                }

                w.WriteEndElement();//Log

            }
            finally
            {
                w.Close();
                fs.Close();
                if (fi.Exists)
                    fi.Delete();
                File.Move(fi.FullName + "temp", fi.FullName);
                ClearLog();
            }
        }

        public void DisableMessageBox()
        {
            this.txtViewMessage.Sensitive = false;
        }

        public void EnableMessageBox()
        {
            this.txtViewMessage.Sensitive = true;
        }

        public void FocusMessageBox()
        {
            this.txtViewMessage.GrabFocus();
        }

        //private void RefreshConversationImage()
        //{
        //    foreach (KeyValuePair<int, Gtk.Image> kvp in this._conversationImages)
        //    {
        //        Gtk.TextIter iter = this.txtViewConversation.Buffer.GetIterAtOffset(kvp.Key);

        //        Gtk.TextChildAnchor anchor = this.txtViewConversation.Buffer.CreateChildAnchor(ref iter);

        //        Gtk.Image img = kvp.Value;
        //        this.txtViewConversation.AddChildAtAnchor(img, anchor);
        //        img.ShowAll();
        //    }
        //    _conversationImages.Clear();
        //}

        private void RefreshImages(Dictionary<int, Gtk.Image> images, TextView tw)
        {
            foreach (KeyValuePair<int, Gtk.Image> kvp in images)
            {
                Gtk.TextIter iter = tw.Buffer.GetIterAtOffset(kvp.Key);

                Gtk.TextChildAnchor anchor = tw.Buffer.CreateChildAnchor(ref iter);

                Gtk.Image img = kvp.Value;
                // img.Data.Add(kvp.Key,kvp.Value);
                tw.AddChildAtAnchor(img, anchor);
                img.ShowAll();
            }
            images.Clear();
        }


        public void RemoveTextTag(Gtk.TextTag tag)
        {
            this.txtViewConversation.Buffer.TagTable.Remove(tag);
            this.txtViewMessage.Buffer.TagTable.Remove(tag);
        }

        public void AddTextTag(Gtk.TextTag tag)
        {
            this.txtViewConversation.Buffer.TagTable.Add(tag);
            this.txtViewMessage.Buffer.TagTable.Add(tag);
        }


        public void DisplayMessage(object obj, UserInfo user, JmShared.MessageType mt)
        {
            //  this.txtViewConversation.Buffer.BeginUserAction();
            List<string> messages = new List<string>();

            string msg = obj.ToString();
            if (msg.Length > 400)
            {
                messages = StaticMethods.SplitLongMessage(msg, 400);
            }
            else
                messages.Add(msg);

            //            Gdk.Pixbuf linePix = null;
            //            Gdk.Pixbuf spacerPix = null;

            bool showInfoTimeStamp = (mt != JmShared.MessageType.Error || mt == JmShared.MessageType.Nudge) && StaticSettings.SettingShowTimestamp;
            bool greenText = mt == MessageType.Authenticated || mt == MessageType.Hello || mt == MessageType.LoginSuccessful || mt == MessageType.Nudge;
            bool redText = mt == MessageType.Error || mt == MessageType.Logout;
            bool line = greenText || mt == MessageType.Logout || mt == MessageType.Nudge;
            bool addSpacer = (mt == MessageType.Message || mt == MessageType.PM);



            bool spacerAdded = false;

            foreach (string str in messages)
            {
                Gtk.TextIter iter;
                Dictionary<int, object> textobjects;
                if (mt == JmShared.MessageType.Message || mt == JmShared.MessageType.PM)
                    textobjects = SplitUrlAndEmoticons(str, user);
                else
                {
                    textobjects = new Dictionary<int, object>();
                    textobjects.Add(0, str);
                }
                bool writeName = true;
                if (mt == MessageType.Message || mt == MessageType.PM)
                {
                    if (this.LastUserWritten != null && this.LastUserWritten.Equals(user))//LastUserWritten.UserName == user.UserName)
                    {
                        if (DateTime.Now.Subtract(this.LastMessageReceivedTime).TotalSeconds < 10)
                        {
                            writeName = false;
                        }
                        this.LastMessageReceivedTime = DateTime.Now;
                    }
                    else
                    {
                        this.LastUserWritten = user;
                        this.LastMessageReceivedTime = DateTime.Now;
                    }
                    if (writeName || !StaticSettings.SettingGroupMessages)
                    {
                        string saysMsg = user.PrettyName + " says ";
                        if (StaticSettings.SettingShowTimestamp)
                        {
                            string timeStamp = "(" + DateTime.Now.ToShortTimeString() + "): ";
                            saysMsg += timeStamp;
                        }
                        iter = this.txtViewConversation.Buffer.EndIter;
                        int start = this.txtViewConversation.Buffer.EndIter.Offset;
                        if (String.IsNullOrEmpty(this.txtViewConversation.Buffer.Text))
                            this.txtViewConversation.Buffer.Insert(ref iter, saysMsg);
                        else
                            this.txtViewConversation.Buffer.Insert(ref iter, Environment.NewLine + saysMsg);


                        TextIter startiter = this.txtViewConversation.Buffer.GetIterAtOffset(start);
                        this.txtViewConversation.Buffer.ApplyTag(Client.GreyUser, startiter, this.txtViewConversation.Buffer.EndIter);
                    }
                }
                if (addSpacer && !spacerAdded)
                {
                    iter = this.txtViewConversation.Buffer.EndIter;
                    this.txtViewConversation.Buffer.Insert(ref iter, Environment.NewLine);
                    iter = this.txtViewConversation.Buffer.EndIter;
                    this.ConversationImages.Add(this.txtViewConversation.Buffer.EndIter.Offset, Gtk.Image.LoadFromResource("JiveMessengerLinux.Resources.spacer.gif"));
                    //   message = "<spacer>" + message;
                    //InsertImage(txtViewConversation,txtViewConversation.Buffer.EndIter, spacerPix);

                    //    this._conversationImages.Add(txtViewConversation.Buffer.EndIter.Offset, spacerPix);
                    //foo(txtViewConversation.Buffer.EndIter.Offset, spacerPix);
                    //  RefreshConversationImage();

                    spacerAdded = true;
                    RefreshImages(this.ConversationImages, this.txtViewConversation);
                }


                foreach (KeyValuePair<int, object> kvp in textobjects)
                {
                    if (kvp.Value.GetType() == typeof(string))
                    {

                        string message = kvp.Value.ToString();

                        //adds line before status text
                        if (line && !this.LastParagraphHadLine)
                        {
                            iter = this.txtViewConversation.Buffer.EndIter;
                            if (String.IsNullOrEmpty(this.txtViewConversation.Buffer.Text))
                            {
                                //    this.txtViewConversation.Buffer.Text += Environment.NewLine;
                                // this.txtViewConversation.Buffer.Text +="<line>";

                                this.ConversationImages.Add(this.txtViewConversation.Buffer.EndIter.Offset, Gtk.Image.LoadFromResource("JiveMessengerLinux.Resources.line.gif"));//InsertImage(txtViewConversation,txtViewConversation.Buffer.EndIter, linePix);// this.txtViewConversation.Buffer.Text+="<line>";
                                //  foo(txtViewConversation.Buffer.EndIter.Offset, linePix);
                                //    RefreshConversationImage();
                            }
                            else
                            {
                                this.txtViewConversation.Buffer.Insert(ref iter, Environment.NewLine);// + "<line>");
                                this.ConversationImages.Add(this.txtViewConversation.Buffer.EndIter.Offset, Gtk.Image.LoadFromResource("JiveMessengerLinux.Resources.line.gif"));
                            }
                        }
                        iter = this.txtViewConversation.Buffer.EndIter;
                        //writes the message text
                        if (!spacerAdded)
                            this.txtViewConversation.Buffer.Insert(ref iter, Environment.NewLine + message);
                        else
                            this.txtViewConversation.Buffer.Insert(ref iter, message);

                        Gtk.TextIter start = new Gtk.TextIter();
                        Gtk.TextIter end = new Gtk.TextIter();

                        if (this.txtViewConversation.Buffer.EndIter.BackwardSearch(message, 0, out start, out end, txtViewConversation.Buffer.StartIter))
                        {

                            //Colors the message text
                            //  Gtk.TextTag tt = GetTextTagForUser(user);
                            //  this.txtViewConversation.Buffer.TagTable.Add(tt);//.ApplyTag(tt, start, end);
                            TextTag tag = null;
                            if (greenText)
                                tag = Client.GreenUser;
                            else if (redText)
                                tag = Client.RedUser;
                            else
                                tag = Client.TextTags[user.UserID + "_" + Client.TextTagCount[user.UserID]];
                            this.txtViewConversation.Buffer.ApplyTag(tag, start, end);
                        }

                        //adds line after status text
                        if (line)
                        {
                            iter = this.txtViewConversation.Buffer.EndIter;
                            this.txtViewConversation.Buffer.Insert(ref iter, Environment.NewLine);// + "<line>";
                            iter = this.txtViewConversation.Buffer.EndIter;
                            this.ConversationImages.Add(iter.Offset, Gtk.Image.LoadFromResource("JiveMessengerLinux.Resources.line.gif"));
                            this.LastParagraphHadLine = true;
                        }
                        else
                            this.LastParagraphHadLine = false;

                        //                this.lastIter = txtViewConversation.Buffer.EndIter;
                        //update images

                    }
                    else if (kvp.Value.GetType() == typeof(JiveHyperLink))
                    {
                        iter = this.txtViewConversation.Buffer.EndIter;
                        JiveHyperLink jhl = (JiveHyperLink)kvp.Value;
                        InsertLink(this.txtViewConversation.Buffer, ref iter, jhl.HyperLink);
                    }
                    else if (kvp.Value.GetType() == typeof(Gtk.Image))
                    {
                        iter = this.txtViewConversation.Buffer.EndIter;
                        this.ConversationImages.Add(iter.Offset, (Gtk.Image)kvp.Value);
                    }

                    RefreshImages(this.ConversationImages, this.txtViewConversation);
                }



                //scroll to end
                //Gtk.TextMark mark = txtViewConversation.Buffer.GetMark("insert");
                TextMark endMark = txtViewConversation.Buffer.CreateMark("end-mark", txtViewConversation.Buffer.EndIter, false);
                txtViewConversation.ScrollToMark(endMark, 0.0, false, 0.0, 0.0);

                if (StaticSettings.SettingEnableChatLog)
                {
                    string chatTarget = "Group-Chat";
                    if (Tag != null)
                    {
                        UserInfo target = (UserInfo)this.Tag;
                        chatTarget = target.UserName;
                    }
                    this.Log.Add(new LogElement(DateTime.Now, this.SessionID, user.UserName, chatTarget, str, user.FontName, user.FontStyle, user.Color));
                }
            }
        }

        private void InsertLink(Gtk.TextBuffer buffer, ref Gtk.TextIter iter, string text)
        {
            Gtk.TextTag tag = new Gtk.TextTag(null);
            tag.Foreground = "blue";
            tag.Underline = Pango.Underline.Single;
            this.DocumentHyperlinks[tag] = text;
            buffer.TagTable.Add(tag);
            buffer.InsertWithTags(ref iter, text, tag);
        }
        // Links can also be activated by clicking.
        void EventAfter(object sender, Gtk.WidgetEventAfterArgs args)
        {
            if (args.Event.Type != Gdk.EventType.ButtonRelease)
                return;

            Gdk.EventButton evt = (Gdk.EventButton)args.Event;

            if (evt.Button != 1)
                return;

            Gtk.TextView view = sender as Gtk.TextView;
            Gtk.TextIter start, end, iter;
            int x, y;

            // we shouldn't follow a link if the user has selected something
            view.Buffer.GetSelectionBounds(out start, out end);
            if (start.Offset != end.Offset)
                return;

            view.WindowToBufferCoords(Gtk.TextWindowType.Widget, (int)evt.X, (int)evt.Y, out x, out y);
            iter = view.GetIterAtLocation(x, y);

            FollowIfLink(view, iter);
        }
        // Looks at all tags covering the position (x, y) in the text view,
        // and if one of them is a link, change the cursor to the "hands" cursor
        // typically used by web browsers.
        void SetCursorIfAppropriate(Gtk.TextView view, int x, int y)
        {
            bool hovering = false;
            Gtk.TextIter iter = view.GetIterAtLocation(x, y);

            foreach (Gtk.TextTag tag in iter.Tags)
            {
                if (DocumentHyperlinks.ContainsKey(tag))
                {
                    hovering = true;
                    break;
                }
            }

            if (hovering != hoveringOverLink)
            {
                Gdk.Window window = view.GetWindow(Gtk.TextWindowType.Text);

                hoveringOverLink = hovering;
                if (hoveringOverLink)
                    window.Cursor = handCursor;
                else
                    window.Cursor = regularCursor;
            }
        }
        // Update the cursor image if the pointer moved.
        void MotionNotify(object sender, Gtk.MotionNotifyEventArgs args)
        {
            Gtk.TextView view = sender as Gtk.TextView;
            int x, y;
            Gdk.ModifierType state;

            view.WindowToBufferCoords(Gtk.TextWindowType.Widget, (int)args.Event.X, (int)args.Event.Y, out x, out y);
            SetCursorIfAppropriate(view, x, y);

            view.GetWindow(Gtk.TextWindowType.Widget).GetPointer(out x, out y, out state);
        }
        // Also update the cursor image if the window becomes visible
        // (e.g. when a window covering it got iconified).
        void VisibilityNotify(object sender, Gtk.VisibilityNotifyEventArgs a)
        {
            Gtk.TextView view = sender as Gtk.TextView;
            int wx, wy, bx, by;

            view.GetPointer(out wx, out wy);
            view.WindowToBufferCoords(Gtk.TextWindowType.Widget, wx, wy, out bx, out by);
            SetCursorIfAppropriate(view, bx, by);
        }
        // Looks at all tags covering the position of iter in the text view,
        // and if one of them is a link, follow it by showing the page identified
        // by the data attached to it.
        void FollowIfLink(Gtk.TextView view, Gtk.TextIter iter)
        {
            foreach (Gtk.TextTag tag in iter.Tags)
            {

                //                int page = tag_pages [tag];
                //                ShowPage (view.Buffer, (int)page);
                if (this.DocumentHyperlinks.ContainsKey(tag))
                    Process.Start(this.DocumentHyperlinks[tag]);
            }
        }

        private void OnBnEmoticonsClicked(object sender, EventArgs e)
        {

            if (this.EmoticonWin != null && EmoticonWin.Visible == true)
            {
                //this.EmoticonWin.Visible = false;
                //EmoticonWindowDefocused = false;
                //EmoticonWindowOpen = false;

                return;
            }

            Gtk.Button bn = sender as Gtk.Button;
            //if (emoticonWin == null)
            //{
            if (NeedEmoticonRefresh || EmoticonWin == null)
            {
                this.EmoticonWin = new EmoticonWindow();
                this.EmoticonWin.FocusOutEvent += new Gtk.FocusOutEventHandler(emoticonWin_FocusOutEvent);
                this.EmoticonWin.EmoticonClickedEvent += new EventHandler<EmoticonClickedEventArgs>(emoticonWin_EmoticonClickedEvent);
                this.EmoticonWin.AddEmoticonEvent += new EventHandler<EventArgs>(EmoticonWin_AddEmoticonEvent);
                //}
                this.EmoticonWin.ParentWindow = this.ParentWindow;
                //win.WindowPosition = Gtk.WindowPosition.CenterOnParent;
                MoveEmoticonWindow();
                //EmoticonWindowOpen = true;
                //EmoticonWindowDefocused = false;
                this.EmoticonWin.Show();
                NeedEmoticonRefresh = false;
            }
            else
            {
                EmoticonWin.ClearSelection();
                EmoticonWin.Visible = true;

                MoveEmoticonWindow();
                //EmoticonWindowOpen = true;
                //EmoticonWindowDefocused = false;
            }

            //EmoticonWindowOpen = true;
        }

        private void EmoticonWin_AddEmoticonEvent(object sender, EventArgs e)
        {
            //var ae = new AddEmoticon(false, StaticSettings.CustomEmoticonFolder)
            //{
            //    Owner = Window.GetWindow(this),
            //    WindowStartupLocation = WindowStartupLocation.CenterOwner
            //};
            //if (ae.ShowDialog() == true)
            //{
            //    //save smiley
            //    Dictionary<string, string> foo = StaticSettings.CustomEmoticons;
            //    if (!foo.ContainsKey(ae.CustomEmoticonShortcut))
            //    {
            //        foo.Add(ae.CustomEmoticonShortcut, ae.CustomEmoticonPath);
            //        StaticSettings.CustomEmoticons = foo;
            //    }
            //}
            //ae.Close();
            ResponseType response = ResponseType.None;
            AddNewEmoticon ae = null;
            try
            {
                ae = new AddNewEmoticon(false, StaticSettings.CustomEmoticonFolder);//false, StaticSettings.CustomEmoticonFolder);
                //  ae.Visible = true;
                ae.Parent = this;
                ae.ParentWindow = this.ParentWindow;
                ae.WindowPosition = WindowPosition.CenterOnParent;
                //    ae.SetPosition(WindowPosition.CenterOnParent);
                //  ae.ShowAll();
                MoveAddNewEmoticonWindow(ae);
                //save smiley
                ae.Modal = true;
                response = (ResponseType)ae.Run();
                if (response == ResponseType.Accept)
                {
                    Dictionary<string, string> foo = StaticSettings.CustomEmoticons;
                    if (!foo.ContainsKey(ae.CustomEmoticonShortcut))
                    {
                        foo.Add(ae.CustomEmoticonShortcut, ae.CustomEmoticonPath);
                        NeedEmoticonRefresh = true;
                    }

                }
                //   ae.ShowAll();

            }
            finally
            {
                if (ae != null)
                    ae.Destroy();
            }
            this.txtViewMessage.GrabFocus();
        }

        private void MoveEmoticonWindow()
        {
            int windowX;
            int windowY;
            Button bn = this.bnEmoticons;
            Window win = EmoticonWin;
            bn.GdkWindow.GetOrigin(out windowX, out windowY);



            windowX += bn.Allocation.X;
            windowY += bn.Allocation.Y;
            //int height;
            //int width;
            Gtk.Requisition r = this.EmoticonWin.SizeRequest();
            //win.GetSize(out width, out height);
            this.EmoticonWin.Resize(r.Height, r.Width);
            windowY -= r.Height;

            win.Move(windowX, windowY);
        }

        private void MoveAddNewEmoticonWindow(Window win)
        {

            int windowX;
            int windowY;
            Button bn = this.bnEmoticons;
            bn.GdkWindow.GetOrigin(out windowX, out windowY);



            windowX += bn.Allocation.X;
            windowY += bn.Allocation.Y;
            //int height;
            //int width;
            Gtk.Requisition r = win.SizeRequest();
            //win.GetSize(out width, out height);
            win.Resize(r.Height, r.Width);
            windowY -= r.Height + 100;

            win.Move(windowX, windowY);
        }

        private void emoticonWin_EmoticonClickedEvent(object sender, EmoticonClickedEventArgs e)
        {
            this.EmoticonWin.Visible = false;
            //EmoticonWindowDefocused = false;
            //EmoticonWindowOpen = false;
            Gtk.TextIter iter = this.txtViewMessage.Buffer.EndIter;
            //  this.txtViewMessage.Buffer.Insert(ref iter, e.EmoticonShortcut);
            string link = StaticSettings.GetEmoticons(Client.CurrentUserInfo, true, true)[e.EmoticonShortcut];
            Gtk.Image img = System.IO.Path.IsPathRooted(link) ? new Gtk.Image(link) : new Gtk.Image(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("JiveMessengerLinux.Resources." + link));
            img.Data.Add(e.EmoticonShortcut, link);
            this.MessageImages.Add(iter.Offset, img);
            if (!this.txtViewMessage.Buffer.Data.ContainsKey(iter.Offset))
                this.txtViewMessage.Buffer.Data.Add(iter.Offset, e.EmoticonShortcut);
            RefreshImages(this.MessageImages, this.txtViewMessage);
            FocusMessageBox();

        }

        private void emoticonWin_FocusOutEvent(object o, FocusOutEventArgs args)
        {
            this.EmoticonWin.Visible = false;
            //EmoticonWindowDefocused = true;
            //EmoticonWindowOpen = false;
            //  this.EmoticonWin = null;
        }

        private void OnBnChangeFontClicked(object sender, EventArgs e)
        {
            Gtk.FontSelectionDialog fdia = new Gtk.FontSelectionDialog("Select font name");
            if (StaticSettings.FontDescription != null)
                fdia.SetFontName(StaticSettings.FontDescription.ToString());

            fdia.Response += delegate(object o, Gtk.ResponseArgs resp)
            {

                if (resp.ResponseId == Gtk.ResponseType.Ok)
                {
                    StaticSettings.FontDescription = Pango.FontDescription.FromString(fdia.FontName);
                    Client.CurrentUserInfo.FontName = StaticSettings.FontDescription.Family;
                    Client.CurrentUserInfo.FontStyle = StaticSettings.FontDescription.Weight.ToString();
                    this.txtViewConversation.ModifyFont(StaticSettings.FontDescription);
                    this.txtViewMessage.ModifyFont(StaticSettings.FontDescription);
                    ChatMessage msg = new ChatMessage
                    {
                        Sender = Client.CurrentUserInfo,
                        MessageType = MessageType.UpdateUserInfo
                    };
                    Client.SendMessage(msg);

                }
            };

            fdia.Run();
            fdia.Destroy();
        }

        private void OnButton1Clicked(object sender, EventArgs e)
        {
            Gtk.ColorSelectionDialog cdia = new Gtk.ColorSelectionDialog("Select color");
            cdia.Response += delegate(object o, Gtk.ResponseArgs resp)
            {

                if (resp.ResponseId == Gtk.ResponseType.Ok)
                {
                    StaticSettings.Color = String.Format("{0:X2}{1:X2}{2:X2}", (int)Math.Truncate(cdia.ColorSelection.CurrentColor.Red / 256.00),
                                                         (int)Math.Truncate(cdia.ColorSelection.CurrentColor.Green / 256.00),
                                                         (int)Math.Truncate(cdia.ColorSelection.CurrentColor.Blue / 256.00));
                    Client.CurrentUserInfo.Color = StaticSettings.Color;

                    ChatMessage msg = new ChatMessage
                    {
                        Sender = Client.CurrentUserInfo,
                        MessageType = MessageType.UpdateUserInfo
                    };
                    Client.SendMessage(msg);
                }
            };

            cdia.Run();
            cdia.Destroy();
        }

        protected void OnBnNudgeClicked(object sender, EventArgs e)
        {
            if (DateTime.Now.Subtract(this.LastNudge).TotalSeconds < 5)
            {
                this.DisplayMessage("You may not send a nudge that often.", Client.CurrentUserInfo, MessageType.Error);
                return;
            }

            this.LastNudge = DateTime.Now;
        }

        private enum TextMatchType
        {
            URL,
            Emoticon
        }

        private struct TextMatch
        {
            public TextMatchType type { get; set; }

            public string Text { get; set; }

            public override string ToString()
            {
                return Text;
            }
        }

        private class JiveHyperLink
        {
            public string HyperLink { get; set; }

            public JiveHyperLink(string str)
            {
                this.HyperLink = str;
            }
        }
    }
}

